#version 330 core

#define MOD 0
#define XOR 1
#define SXOR 2

const float float_precision_threshold = 0.1;

struct AmbientLight {
    vec3 color;
    vec3 intensity;
};


struct ParallelLight {
    vec3 direction;
    vec3 color;
    vec3 intensity;
};

struct PointLight {
    vec3 pos;
    vec3 col;
    vec3 intensity;
    vec3 attenuation;
};

struct SpotLight {
    vec3 pos;
    vec3 centerDir;
    vec3 col;
    vec2 fovRange;
};

in vec3 oPos;
in vec3 oCol;
in vec3 oNormal;
in vec2 oTexCoord;
in vec3 oWorldPos;

// 光照渲染相关的参数
uniform AmbientLight uAmbientLight;
uniform ParallelLight uParallelLight;
uniform PointLight uPointLight;
uniform SpotLight uSpotLight;
uniform vec3 uCameraPos;
uniform sampler2D uDiffuseTex;
uniform sampler2D uSpecTex;
uniform float uShininess;

// 网格地面渲染的相关参数
uniform int uGridGenType;       // 网格生成算法类型
uniform float uGridAmbient;     // 网格的环境光调节系数
uniform vec4 uGridScale;        // 网格的尺度：宽高，单个网格的size（分别控制宽高的size）
uniform vec3 uGridColorA;       // 网格交替颜色
uniform vec3 uGridColorB;

out vec4 oFragColor;

vec4 getGridColor() {
    vec4 gridColor = vec4(0.);
    if (uGridGenType == MOD) {
        vec2 cell = floor(oPos.xz / uGridScale.zw);
        float mod = mod(cell.x + cell.y, 2.);
        vec3 col = mix(uGridColorA, uGridColorB, mod);
        gridColor = vec4(uGridAmbient * col, 1.);
    } else if (uGridGenType == XOR) {
        // xor的性质：1. a^b=b^a 2. a^0=0^a=a 3. a^a=0, 这里设置的cell中偏移了0.1主要因为浮点数的精度问题导致的误差，可以通过uniform外部传递进来控制
        // glsl提供的round和floor的区别：前者是四舍五入，后者直接丢弃小数掉，只有对角的cell.x == cell.y, 所以针对网格的对角格，xor之后的结果为0
        // 对角上下均为非0态，通过&1将异或值限制在01
        ivec2 cell = ivec2(round(oPos.xz + float_precision_threshold));
        float xor = float((cell.x ^ cell.y) & 1);
        vec3 col = mix(uGridColorA, uGridColorB, xor);
        gridColor = vec4(uGridAmbient * col, 1.);
    } else if (uGridGenType == SXOR) {
        // 将0-1离散信号转化为连续的三角信号，能优化一部分上面的噪点问题，目前还是比较严重
        vec2 cell = sign(fract(oPos.xz) - 0.5);
        float sxor = 0.5 - 0.5 * cell.x * cell.y;
        vec3 col = mix(uGridColorA, uGridColorB, sxor);
        gridColor = vec4(uGridAmbient * col, 1.);
    }
    return gridColor;
}

// 环境光
vec3 getAmbientColor() {
    return uAmbientLight.color * uAmbientLight.intensity;
}

// 平行光
vec3 getParallelLightColor(vec3 n, vec3 viewDir, float specMask, float enhance) {
    vec3 lightDir = normalize(uParallelLight.direction);
    float dif = clamp(dot(n, -lightDir), 0., 1.);
    vec3 diffuse =  dif * uParallelLight.color * uParallelLight.intensity;

    float dotLN = dot(-lightDir, n);
    float front = step(0., dotLN);
    vec3 lightRefDir = normalize(reflect(lightDir, n));

    float spec = clamp(dot(lightRefDir, -viewDir), 0., 1.);
    spec = pow(spec, uShininess);
    vec3 specular = spec * uParallelLight.color * front * specMask * enhance;

    return diffuse + specular;
}

// 点光源
vec3 getPointLightColor(vec3 n, vec3 viewDir) {
    vec3 lightDir = normalize(oWorldPos - uPointLight.pos);
    float dif = clamp(dot(n, -lightDir), 0., 1.);
    vec3 diffuse = dif * uPointLight.col;

    // 计算镜面光
    float dotLN = dot(-lightDir, n); // 解决模型背部漏光问题
    float front = step(0.0, dotLN);
    vec3 lightRefDir = normalize(reflect(lightDir, n));
    float spec = clamp(dot(lightRefDir, -viewDir), 0., 1.);
    spec = pow(spec, uShininess); // cos^x, 当x越大，整体图像越趋近于中间非常窄的正态分布，即高光区域会变小，直到缩窄为一个亮点
    vec3 specular = spec * uPointLight.col * front;

    // 计算点光源的强度
    float d = length(oWorldPos - uPointLight.pos);
    float eCoef = uPointLight.attenuation.x + uPointLight.attenuation.y * d + uPointLight.attenuation.z * d * d;
    float e = 1. / eCoef;
    return e * (diffuse + specular);
}

// 聚光灯
vec3 getSpotLightColor(vec3 n, vec3 viewDir) {
    vec3 lightDir = normalize(oWorldPos - uSpotLight.pos);
    float dif = clamp(dot(n, -lightDir), 0., 1.);
    vec3 diffuse = dif * uSpotLight.col;

    float dotLN = dot(-lightDir, n); // 解决模型背部漏光问题
    float front = step(0.0, dotLN);
    vec3 lightRefDir = normalize(reflect(lightDir, n));
    float spec = clamp(dot(lightRefDir, -viewDir), 0., 1.);
    spec = pow(spec, uShininess); // cos^x, 当x越大，整体图像越趋近于中间非常窄的正态分布，即高光区域会变小，直到缩窄为一个亮点
    vec3 specular = spec * uSpotLight.col * front;

    // 计算聚光灯强度
    float cur = dot(lightDir, normalize(uSpotLight.centerDir));
    float innCos = cos(uSpotLight.fovRange.x);
    float outCos = cos(uSpotLight.fovRange.y);
    float E = clamp((cur - outCos) / (innCos - outCos), 0., 1.);

    return E * (diffuse + specular);
}

void main() {
    vec3 texCol = texture(uDiffuseTex, oTexCoord).rgb;
    vec3 specularMask = texture(uSpecTex, oTexCoord).rgb;

    vec3 lightCol = vec3(0.);
    lightCol += getAmbientColor();

    vec3 n = normalize(oNormal);
    vec3 viewDir = normalize(oWorldPos - uCameraPos);

    lightCol += getParallelLightColor(n, viewDir, specularMask.r, 100.);
    lightCol += getSpotLightColor(n, viewDir);
    lightCol += getPointLightColor(n, viewDir);

    lightCol /= 3.;

    vec3 col = lightCol * texCol;

    oFragColor = vec4(sqrt(col), 1.);
}
